﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;
using System.Text;
using EventbriteService.Model;
using EventbriteService.Adapter;

namespace EventbriteService
{
    public class Mapper<T>
    {
        IDataAdapter _doc;
        Type targetType;
        MemberAssignment _header;
        Dictionary<string, List<MemberAssignment>> addresBindings = new Dictionary<string, List<MemberAssignment>>();
        Dictionary<string, PropertyInfo> addressLookup;

        public Mapper(IDataAdapter doc)
        {
            _doc = doc;
            targetType = typeof(T);
            addressLookup = typeof(Location).GetProperties().ToDictionary(p => p.Name.ToLower());
        }

        public static IEnumerable<T> BuildProjection(System.Linq.Expressions.LambdaExpression expression, IDataAdapter doc)
        {
            dynamic mapper = Activator.CreateInstance(typeof(Mapper<>).MakeGenericType(expression.Parameters[0].Type), doc);
            dynamic events = mapper.Build();
            Delegate compiledExpression = expression.Compile();
            foreach (var item in events)
            {
                yield return (T)compiledExpression.DynamicInvoke(item);
            }
        }

        public T BuildInstance()
        {
            if (_header == null)
                BuildSummary();

            var item = _doc.Root;
            var result = BuildBody(targetType, item);

            return (T)Expression.Lambda(result).Compile().DynamicInvoke();
        }

        public IEnumerable<T> Build()
        {
            if (_header == null)
                BuildSummary();
            
            string name = targetType.Name.ToLower();
            if (name.Equals("category"))
                name = "categories";
            else
                name += "s";

            var events = _doc.Root.Elements().FirstOrDefault(e => e.Name == name);
            
            if (events == null)
               yield return default(T);

            foreach (var item in events.Elements())
            {
                var result = BuildBody(targetType, item);
               // addresBindings.Clear();
                yield return (T)Expression.Lambda(result).Compile().DynamicInvoke();
            }
        }

        private void BuildSummary()
        {
            var summary = _doc.Root.Elements().SingleOrDefault(e => e.Name == "pagination");
            if (summary == null)
                return;
            var member = targetType.GetProperty("Pagination");
            if (member == null)
                return;
            var result = BuildBody(member.PropertyType, summary);
            _header = Expression.Bind(member, result);
        }

        // recursively builds Eventbrite types
        private MemberInitExpression BuildBody(Type type, IEventBriteData events)
        {
            List<MemberBinding> bindings = new List<MemberBinding>();
            foreach (var item in events.Elements())
            {
              
                if (item.Name == "jobject" || item.Name.Equals(type.Name,StringComparison.InvariantCultureIgnoreCase))
                {
                    return BuildBody(type, item);
                }
               
                var member = type.FindPropertyForElement(item.Name);
                if (member != null)
                {
                    var targetType = member.FindTypeForElement();
                    if (targetType.IsClass && targetType != typeof(String))
                    {
                        if (targetType.IsGenericList() && item.Elements() != null)
                        {
                            var elements = BuildList(targetType, item);
                            var list = Expression.ListBind(member, elements);
                            bindings.Add(list);
                        }
                        else
                        {
                            if (item.Elements() != null) // location type is not related to element in XML doc
                            {
                                var memberInit = BuildBody(targetType, item);
                                bindings.Add(Expression.Bind(member, memberInit));
                            }
                        }
                        continue;
                    }

                   if (item.Value == "None")
                            item.Value = "0";

                   if (item.Value == "no")
                       item.Value = "False";

                   if (item.Value == "yes")
                       item.Value = "True";
                   
                    object val;
                    if (targetType.IsEnum)
                    {
                        if (string.IsNullOrEmpty(item.Value))
                            val = null;
                        else
                            val = Enum.Parse(targetType, item.Value);
                    }
                    else
                    {
                        
                        if (targetType != typeof(String))
                        {
                            if ((targetType == typeof(int) || targetType == typeof(long)) && item.Value == null)
                            {
                                val = 0;
                            }
                            else if (targetType == typeof(DateTime) && String.IsNullOrEmpty((string)item.Value))
                            {
                                val = null;
                            }
                            else
                            {
                                val = Convert.ChangeType(item.Value, targetType);
                            }
                        }
                        else
                        {
                            val = item.Value;
                        }

                    }

                    if (member.IsNullable())
                        bindings.Add(Expression.Bind(member, Expression.Convert(Expression.Constant(val), member.PropertyType)));
                    else
                      if(val != null)bindings.Add(Expression.Bind(member, Expression.Constant( val)));
                }

            }

            // we have to add summary if it is defined in XML doc 
            if (type == typeof(T) && _header != null)
            {
                bindings.Add(_header);
            }
         
            var initMember = Expression.MemberInit(Expression.New(type), bindings);
            return initMember;
        }

       

        private IList<ElementInit> BuildList(Type type, IEventBriteData element)
        {
            var elementType = type.GetGenericArguments()[0];
            var add = type.GetMethod("Add");
            var elementsList = new List<ElementInit>();
            Expression chldl ;
            foreach (var item in element.Elements())
            {
                if (elementType.IsValueType )
                {
                    chldl = Expression.Constant(Convert.ChangeType(item.Value, elementType));
                }
                else
                {
                     chldl = BuildBody(elementType, item);
                }
                elementsList.Add(Expression.ElementInit(add, chldl));
            }
            return elementsList;
        }
       
    }
}
